package jwt

import (
	"context"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/golang-jwt/jwt/v5"
	"server/internal/model"
	"server/internal/service"
	"time"
)

type sJwt struct{}

func init() {
	service.RegisterJwt(&sJwt{})
}

// getJwtSecret 签名秘钥
func (s *sJwt) getJwtSecret() []byte {
	res, _ := g.Cfg().Get(context.TODO(), "jwt.secret")
	return res.Bytes()
}

// getIssuer jwt签发者
func (s *sJwt) getIssuer() string {
	return "goFrame"
}

// 获取过期时间
func (s *sJwt) getExpire() time.Duration {
	res, _ := g.Cfg().Get(context.TODO(), "jwt.expireTime")
	expire := time.Duration(res.Int64()) * time.Minute
	return expire
}

// GenerateToken 生成Token
func (s *sJwt) GenerateToken(ctx context.Context, userId int64, userName string, audience string) (string, error) {
	nowTime := time.Now().Local()
	expireTime := nowTime.Add(s.getExpire())

	// Create claims with multiple fields populated
	claims := model.JwtCustomClaims{}
	claims.UserId = userId
	claims.UserName = userName
	claims.Issuer = s.getIssuer()
	claims.Audience = []string{audience} //受众：限制令牌使用的模块：如admin、app等
	claims.ExpiresAt = jwt.NewNumericDate(expireTime)
	claims.IssuedAt = jwt.NewNumericDate(nowTime)
	claims.NotBefore = jwt.NewNumericDate(nowTime)

	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
	tokenStr, err := token.SignedString(s.getJwtSecret())
	return tokenStr, err
}

// ParseToken parsing token
func (s *sJwt) ParseToken(ctx context.Context, token string) (claims *model.JwtCustomClaims, err error) {
	claims = new(model.JwtCustomClaims)
	tokenClaims, err := jwt.ParseWithClaims(token, &model.JwtCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
		return s.getJwtSecret(), nil
	})
	if tokenClaims == nil {
		return claims, err
	}
	//能正常解析数据后将源数据返回，便于后续刷新Token提供支持
	if res, ok := tokenClaims.Claims.(*model.JwtCustomClaims); ok {
		claims = res
	}
	if err != nil {
		return claims, err
	}
	if tokenClaims != nil && tokenClaims.Valid {
		return claims, nil //err=nil时才是有效的token
	}
	return claims, nil
}

// ValidateAudience 验证token的受众方是否正确，避免token错误使用，例如admin的token在app使用
func (s *sJwt) ValidateAudience(claims *model.JwtCustomClaims, audience string) bool {
	for _, item := range claims.Audience {
		if audience == item {
			return true
		}
	}
	return false
}
